﻿using DapperExtensions;
using NetDh.Cache;
using NetDh.DbUtility;
using NetDh.EasyLogger;
using NetDh.TestEntity;
using NetDh.TestService;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NetDh.TestConsole
{
    /// <summary>
    /// NetDh模块使用示例代码
    /// </summary>
    public class NetDhExample
    {
        #region 用全局静态变量实现单例。
        /// <summary>
        /// 服务端使用数据库操作对象，前端不可直接使用
        /// </summary>
        public static DbHandleBase DbHandle { get; set; }

        //说明:如果你有多库，比如读写分离中的只读库，则再定义一个数据库操作对象即可。
        public static DbHandleBase ReadDbHandle { get; set; }

        /// <summary>
        /// 缓存操作对象。
        /// (说明:如果你的缓存有可能从内存缓存和memcahce缓存之间切换，则使用接口ICacheHandle声明。)
        /// </summary>
        public static ICacheHandle CacheHandle { get; set; }
        /// <summary>
        /// 日志操作对象
        /// </summary>
        public static LogHandle LogHandle { get; set; }

        //说明:比如如果你想把用户操作日志和系统日志分开在不同表里记录，则可以再声明一个日志操作对象
        public static LogHandle SysLogHandle { get; set; }
        #endregion
        /// <summary>
        /// 静态构造函数，只会初始化一次
        /// </summary>
        static NetDhExample()
        {
            //1.初始化缓存操作对象
            CacheHandle = new RuntimeCacheHandle();

            //2.初始化数据库操作对象
            var connStr = "Data Source=.;Initial Catalog=Test;User Id=sa;Password=***;";
            DbHandle = new SqlServerHandle(connStr);
            //如果有多库，可再new个对象
            //ReadDbHandle = new SqlServerHandle(connStrForRead);

            //3.初始化日志操作对象
            //先定义日志写入数据库的委托
            Action<string, TbLog> doInsert = (sql, model) =>
            {
                DbHandle.ExecuteNonQuery(sql, model);//你想要用什么方式把日志写入Db，是可以自己指定。
                //DbHandle.Insert(model);
                //如果你的表结构和TbLog类一样，则可直接用：DbHandle.Insert(model);这样就不会用到InsertLogSql，也就不用管InsertLogSql的语法是否支持所有数据库.
            };
            //其中的asynThreadCount参数默认是1，代表后台独立线程独立处理日志;我这边设置为0，代表同步处理日志。
            LogHandle = new LogHandle("MyLocalTest.exe", Path.Combine(Environment.CurrentDirectory, "Logs"), doInsert, 0, "TbLog");
            //如果你想要有多个日志操作对象，则再new一个，把日志放不同目录不同数据表中
            SysLogHandle = new LogHandle("MyLocalTest.exe", Path.Combine(Environment.CurrentDirectory, "SysLogs"), doInsert, 0, "TbSysLog");
        }
        /// <summary>
        /// 各模块使用的示例代码
        /// </summary>
        public static void TestMain()
        {
            #region 缓存处理类
            var key = "cacheKey1";
            //添加缓存
            CacheHandle.Set(key, new TbUser { Id = 1, Name = "user1" });
            //获取缓存
            var cacheUser = CacheHandle.Get<TbUser>(key);//ctrl+f12 转到接口的实现
            //删除缓存
            CacheHandle.Remove(key);
            //如果不存在才添加缓存，已经存在则不添加
            CacheHandle.AddIfNotExist(key, new TbUser { Id = 2, Name = "user2" });
            #endregion

            #region 日志处理类
            LogHandle.LogToTxt("日志写入txt");
            LogHandle.LogToTxt("日志写入txt", "logcategory1");//可用第二个参数来自定义分类日志
            LogHandle.LogToDb("日志写入db", "logcategory2");//可用第二个参数来自定义分类日志
            LogHandle.LogToBoth("日志同时写入txt和Db");
            //LogHandle.Log是最常用的函数，太多日志是不建议写入txt。
            LogHandle.Log("日志优先写入Db，当写入Db失败，才会写入txt。如果LogHandle对象DoInsertLogToDb属性为null，则会自动选择写入txt。");
            #endregion

            #region 数据库交互(sqlserver+Dapper+DapperExtension)
            //---------CRUD操作--------
            //实体类中的第一个Id或者以Id结尾的字段，会被默认当作主键，Dapper不仅建议你的表主键为Id或以Id结尾的字段，
            //而且Dapper默认主键字段在数据库表里有默认值(比如有设置为自增长)，关于为什么建议用自增长主键，可以翻一下我之前的博客文章《SQL Server索引原理解析》
            //如果表中的主键不符合此规定（比如表主键是MainKey字段)，则需要自定义Map映射，参考以下的“Dapper进阶”
            var user = DbHandle.Get<TbUser>(1);//这边1产生的是 where Id=1 的条件
            //Get<TbUser>是DapperExtension的功能，不是Dapper的功能。
            //Get<TbUser>这种写法类似select *，并不是好作法，但是它写法方便，只取一笔影响不大。一般是select你要的字段，而不是select所有字段。具体问题具体分析。

            user.Name = "new name";
            DbHandle.Update(user);
            /* 注意如果用实体类去update，就算只更新一个字段，DapperExtension也会生成除了id主键之外的所有字段的更新。
             * 多余的更新会增加数据库开销，尤其有非聚集索引字段。因此，建议如果要用此Update函数，则只用于基础表。
             */

            var lastInsertId = DbHandle.Insert(user);//返回last_insert_Id。因为它生成的语句包含：SELECT CAST(SCOPE_IDENTITY()  AS BIGINT) AS [Id]
            /* DbHandle.Insert(user);是不会报主键重复。以下是DbHandle.Insert产生的语法（来自SQL Server Profiler工具），
             * 不会生成主键Id的Insert。因为Dapper默认你的主键如果是整形则是KeyType.Identity类型(即默认主键字段在数据库表里有默认值,比如有设置为自增长)，
             *  DbHandle.Insert(user) DapperExtension产生的sql语句：
             exec sp_executesql N'INSERT INTO [TbUser] ([TbUser].[Name], [TbUser].[Age], [TbUser].[Remark], [TbUser].[Department], [TbUser].[CreateTime]) VALUES (@Name, @Age, @Remark, @Department, @CreateTime);
SELECT CAST(SCOPE_IDENTITY()  AS BIGINT) AS [Id]',N'@CreateTime datetime,@Department nvarchar(200),@Age decimal(6,4),@Name nvarchar(200),@Remark nvarchar(4000)',@CreateTime='2018-06-07 20:05:33.630',@Department=N'D1',@Age=30,@Name=N'new name',@Remark=N'remark1'
             */

            user.Id = 1001;
            DbHandle.Delete(user);
            //DbHandle.Delete(user);只和主键Id有关。产生的sql:exec sp_executesql N'DELETE FROM [TbUser] WHERE ([TbUser].[Id] = @Id_0)',N'@Id_0 int',@Id_0=1001
            DbHandle.Delete(new TbUser() { Id = 1001 });

            //---------------------
            //1.取Id<100的TbUser降序数据列表
            var filter = Predicates.Field<TbUser>(f => f.Id, Operator.Lt, 100);
            var sort = new List<ISort> { Predicates.Sort<TbUser>(f => f.Id, false) };//false降序
            var users2 = DbHandle.GetList<TbUser>(filter, sort);
            //如果需要多个过滤条件需要用到PredicatesGroup嵌套，这是DapperExtensions的功能（不是Dapper原生功能）。
            //复杂的sql建议用直接写sql（如下简洁版），直接写复杂sql的优点：select字段可选、sql执行计划可控。

            //2.取Id<100的TbUser降序数据列表（简洁版）只取你要的字段Id,Name
            var uses = DbHandle.Query<TbUser>("select Id,Name from TbUser where Id<@maxId order by Id desc", new { maxId = 100 });

            //winform中经常会用到DataTable的DataView，方便Grid展示与过滤，因此开放ExecuteDataTable和ExecuteDataset
            var table = DbHandle.ExecuteDataTable("select Id,Name from TbUser where Id<100 order by Id desc");

            //---------分页--------
            //1.单表分页（第3页，一页10笔） DapperExtension支持单表
            var pageUsers = DbHandle.GetPageByModel<TbUser>(null, sort, 2, 10);//参数startPageIndex第1页是从0开始

            //2.sqlserver 自己sql分页（第3页，一页10笔），并且获取表记录总数 
            var pageSql = @" select top 10 * from(
select (row_number() over(order by Id))as rowId,* from TbUser where Id<@maxId) as a where a.rowId >20 order by a.rowId;
select count(1) from TbUser";
            var dataset = DbHandle.ExecuteDataSet(pageSql, new { maxId = 1000 });

            //3.封装的sql分页，为了支持不同数据库分页写法不同（第3页，一页10笔），并且获取表记录总数，适合较复杂的分页
            var pageSql1 = DbHandle.GetPageSql("select * from TbUser A where A.Id<@maxId", "order by A.Id", 2, 10, "select count(1) from TbUser");//参数startPageIndex第1页是从0开始
            dataset = DbHandle.ExecuteDataSet(pageSql1, new { maxId = 1000 });
            //---------多表关联--------
            //select的字段并没有对应的实体类时，可用QueryDynamics。DbHandle也支持返回IEnumerable<Hashtable>的QueryHashtables，方便转为json格式
            var dyObj = DbHandle.QueryDynamics("select A.Name,B.Amount from TbUser A inner join TbOrder B on B.Uid=A.Id where A.Id=@Id", new { Id = 10 });


            //---------使用存储过程--------
            //执行存储过程就是把函数参数CommandType设置为CommandType.StoredProcedure即可，存储过程的参数传递直接 new {@p=value}

            #endregion

            #region DapperExtensions进阶--自定义Map映射。
            /*项目起初，规范好表设计，一般是不会用到自定义Map映射。如果是现有项目，可酌情考虑*/
            //自定义Map，具体参考TbUser.cs里的代码说明

            //以下这句是初始化，告诉DapperExtensions
            DapperExtensions.DapperExtensions.SetMappingAssemblies(new[] { typeof(TbUserMapper).Assembly });
            //DapperExtensions.DapperExtensions.SqlDialect = new DapperExtensions.Sql.SqlServerDialect();//DapperExtensions默认就是SqlServerDialect

            #endregion

            #region 调用服务和二次开发服务
            //可以用服务工厂调用相应方法
            ServiceFactory.Get<UserService>().TestFunc();
            //也可以直接调用服务静态方法
            UserService.TestStaticFunc();

            //二次开发模式
            //1.调用的是UserService中的TestVirtualFunc方法
            ServiceFactory.Get<UserService>().TestVirtualFunc();
            //2.场景:后续不改原系统代码，只是在原来基础上做二次开发
            //注册二次开发Service
            //ServiceFactory.AddSecondaryAssembly(typeof(UserServiceX).Assembly);//其中UserServiceX继承自UserService
            //3.假如执行了ServiceFactory.AddSecondaryAssembly，则下行代码会调用到UserServiceX中的TestVirtualFunc方法
            ServiceFactory.Get<UserService>().TestVirtualFunc();
            #endregion
        }
    }
}
